<?php
// +----------------------------------------------------------------------
// | SwiftPHP [ JUST DO ONE THING WELL ]
// +----------------------------------------------------------------------
// | Copyright (c) 2015-2017  http://swiftphp.zhongwenyu.com All rights reserved.
// +----------------------------------------------------------------------
// | Author: zhongwenyu <zhongwenyu1987@163.com> <http://www.zhongwenyu.com>
// +----------------------------------------------------------------------
// | Times: 2017/2/26 13:10
// +----------------------------------------------------------------------
namespace swift;

class Validate{
    // 验证规则，['name'=>['require','max|45']]
    protected $rule = [];
    // 验证提示信息，['name.require' => '名称必须']
    protected $message = [];
    // 验证失败错误信息
    protected $error;
    // 验证场景 scene = ['edit'=>'name1,name2,...']
    protected $scene = [];
    // 当前验证场景
    protected $currentScene = null;
    // 字段验证描述
    protected $field = [];
    // 正则验证
    protected $regex = [];
    // 自定义的验证方法
    protected $func = [];

    /**
     * 架构函数
     * @access public
     * @param array $rules 验证规则
     * @param array $message 验证提示信息
     */
    public function __construct(array $rules = [], $message = []){
        $this->rule    = array_merge($this->rule, $rules);
        $this->message = array_merge($this->message, $message);
    }

    /**
     * 添加字段验证规则
     * @access protected
     * @param string|array  $name  字段名称或者规则数组
     * @param mixed         $rule  验证规则
     * @return Validate
     */
    public function rule($name, $rule = ''){
        if (is_array($name)) {
            $this->rule = array_merge($this->rule, $name);
        } else {
            $this->rule[$name] = $rule;
        }
        return $this;
    }

    /**
     * 设置提示信息
     * @access public
     * @param string|array  $name  字段名称
     * @param string        $message 提示信息
     * @return Validate
     */
    public function message($name, $message = ''){
        if (is_array($name)) {
            $this->message = array_merge($this->message, $name);
        } else {
            $this->message[$name] = $message;
        }
        return $this;
    }

    /**
     * 设置验证场景
     * @access public
     * @param string|array  $name  场景名或者场景设置数组
     * @param mixed         $fields 要验证的字段
     * @return Validate
     */
    public function scene($name, $fields = null){
        if (is_array($name)) {
            $this->scene = array_merge($this->scene, $name);
        }if (is_null($fields)) {
            // 设置当前场景
            $this->currentScene = $name;
        } else {
            // 设置验证场景
            $this->scene[$name] = $fields;
        }
        return $this;
    }

    /**
     * 判断是否存在某个验证场景
     * @access public
     * @param string $name 场景名
     * @return bool
     */
    public function hasScene($name){
        return isset($this->scene[$name]);
    }

    // 获取错误信息
    public function getError(){
        return $this->error;
    }

    /**
     * 数据验证
     * @access public
     * @param array     $data  数据
     * @param mixed     $rules  验证规则
     * @param string    $scene 验证场景
     * @return bool
     */
    public function check($data, $rules = [], $scene = ''){
        if (empty($rules)) {
            // 读取验证规则
            $rules = $this->rule;
        }
        // 分析验证规则
        $array  = [];
        $scene = $this->getScene($scene);
        if (is_array($scene) && !empty($scene)) {
            // 处理场景验证字段
            foreach ($scene as $k => $val) {
                isset($rules[$val]) && $array[$val] = $rules[$val];
            }
        }else{
            $array = $rules;
        }
        if(is_numeric(key($data))){
            foreach($data as $val){
                if(!$this->checkItem($val , $array)){
                    return false;
                }
            }
        }else{
            return $this->checkItem($data , $array);
        }
        return true;
    }

    /**
     * 验证字段
     * @access protected
     * @param string    $data  待验证数据，必须一维关联数组
     * @param array     $msg  提示信息
     * @param array     $i  提示信息编号
     * @return mixed
     */
    protected function checkItem($data, $rules){
        foreach($rules as $key => $val){
            if(in_array('require' , $val) || trim($val) == 'require'){
                // require判断
                if(!isset($data[$key])){
                    // 记录错误信息
                    $message = $this->getRuleMsg($key, 'require', 'require');
                    $this->error = $message ?: '字段 '.$key . '不存在！';
                    return false;
                }
            }else{
                // 非require，有数据才会验证
                if(isset($data[$key])){
                    if(is_array($val)){
                        foreach($val as $v){
                            if(!$this->checkOne($key , $data[$key] , $v)){
                                return false;
                            }
                        }
                    }else{
                        if(!$this->checkOne($key , $data[$key] , $val)){
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    }

    protected function checkOne($field , $value , $rule){
        $func = '';  // 实际调用的验证方法名
        $info = '';  // 验证规则信息
        if(strpos($rule , '|')){
            list($func , $rule) = explode('|' , $rule , 2);
        }
        if (method_exists($this , $func)){
            // 调用自定义方法
            $info = $func;
        } else {
            $func = 'is';
            $info = $rule;
        }
        // 有数据才会行验证
        if (!is_null($value) && '' !== $value){
            // 验证类型
            $callback = isset($this->func[$func]) ? $this->func[$func] : [$this, $func];
            // 验证数据
            $result = call_user_func_array($callback, [$value, $rule, $field]);
        } else {
            $result = true;
        }
        if (false === $result) {
            // 验证失败 保存错误信息，返回错误
            $message = $this->getRuleMsg($field, $info, $rule);
            $this->error = $message ?: '字段 '.$field . '=' . $value . ' 不符合验证规则：' . $rule;
            return false;
        }
        return true;
    }

    /**
     * 获取验证规则的错误提示信息
     * @access protected
     * @param string    $field  字段名
     * @param string    $info  验证规则
     * @param string    $rule  验证规则
     * @return string
     */
    protected function getRuleMsg($field, $info, $rule){
        $key = $field . '.' . $info;
        if(isset($this->message[$key])){
            return $this->message[$key];
        }else{
            return '';
        }
    }

    /**
     * 获取数据验证的场景
     * @access protected
     * @param string $scene  验证场景
     * @return array
     */
    protected function getScene($scene = '')
    {
        if (empty($scene)) {
            // 读取指定场景
            $scene = $this->currentScene;
        }
        if (!empty($scene) && isset($this->scene[$scene])) {
            // 如果设置了验证适用场景
            $scene = $this->scene[$scene];
            if (is_string($scene)) {
                $scene = explode(',', $scene);
            }
        } else {
            $scene = [];
        }
        return $scene;
    }

    /**
     * 验证字段值是否为有效格式
     * @access protected
     * @param mixed     $value  字段值
     * @param string    $rule  验证规则
     * @param array     $data  验证数据
     * @return bool
     */
    protected function is($value, $rule, $data = [])
    {
        switch ($rule) {
            case 'require':
                // 不能为空
                $result = !empty($value) || '0' == $value;
                break;
            case 'accepted':
                // 接受
                $result = in_array($value, ['1', 'on', 'yes']);
                break;
            case 'date':
                // 是否是一个有效日期
                $result = false !== strtotime($value);
                break;
            case 'alpha':
                // 只允许字母
                $result = $this->regex($value, '/^[A-Za-z]+$/');
                break;
            case 'alphaNum':
                // 只允许字母和数字
                $result = $this->regex($value, '/^[A-Za-z0-9]+$/');
                break;
            case 'alphaDash':
                // 只允许字母、数字和下划线 破折号
                $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/');
                break;
            case 'chs':
                // 只允许汉字
                $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u');
                break;
            case 'chsAlpha':
                // 只允许汉字、字母
                $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u');
                break;
            case 'chsAlphaNum':
                // 只允许汉字、字母和数字
                $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u');
                break;
            case 'chsDash':
                // 只允许汉字、字母、数字和下划线_及破折号-
                $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u');
                break;
            case 'activeUrl':
                // 是否为有效的网址
                $result = checkdnsrr($value);
                break;
            case 'ip':
                // 是否为IP地址
                $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]);
                break;
            case 'url':
                // 是否为一个URL地址
                $result = $this->filter($value, FILTER_VALIDATE_URL);
                break;
            case 'float':
                // 是否为float
                $result = $this->filter($value, FILTER_VALIDATE_FLOAT);
                break;
            case 'number':
                $result = is_numeric($value);
                break;
            case 'integer':
                // 是否为整型
                $result = $this->filter($value, FILTER_VALIDATE_INT);
                break;
            case 'email':
                // 是否为邮箱地址
                $result = $this->filter($value, FILTER_VALIDATE_EMAIL);
                break;
            case 'boolean':
                // 是否为布尔值
                $result = in_array($value, [true, false, 0, 1, '0', '1'], true);
                break;
            case 'array':
                // 是否为数组
                $result = is_array($value);
                break;
            default:
                // 正则验证
                $result = $this->regex($value, $rule);
        }
        return $result;
    }

    /**
     * 使用正则验证数据
     * @access protected
     * @param mixed     $value  字段值
     * @param mixed     $rule  验证规则 正则规则或者预定义正则名
     * @return mixed
     */
    protected function regex($value, $rule)
    {
        if (isset($this->regex[$rule])) {
            $rule = $this->regex[$rule];
        }
        if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) {
            // 不是正则表达式则两端补上/
            $rule = '/^' . $rule . '$/';
        }
        return 1 === preg_match($rule, (string) $value);
    }

    /**
     * 使用filter_var方式验证
     * @access protected
     * @param mixed     $value  字段值
     * @param mixed     $rule  验证规则
     * @return bool
     */
    protected function filter($value, $rule)
    {
        if (is_string($rule) && strpos($rule, ',')) {
            list($rule, $param) = explode(',', $rule);
        } elseif (is_array($rule)) {
            $param = isset($rule[1]) ? $rule[1] : null;
            $rule  = $rule[0];
        } else {
            $param = null;
        }
        return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param);
    }

    /**
     * 验证是否在范围内
     * @access protected
     * @param mixed     $value  字段值
     * @param mixed     $rule  验证规则
     * @return bool
     */
    protected function in($value, $rule)
    {
        return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
    }

    /**
     * 验证是否不在某个范围
     * @access protected
     * @param mixed     $value  字段值
     * @param mixed     $rule  验证规则
     * @return bool
     */
    protected function notIn($value, $rule)
    {
        return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
    }

    /**
     * between验证数据
     * @access protected
     * @param mixed     $value  字段值
     * @param mixed     $rule  验证规则
     * @return bool
     */
    protected function between($value, $rule)
    {
        if (is_string($rule)) {
            $rule = explode(',', $rule);
        }
        list($min, $max) = $rule;
        return $value >= $min && $value <= $max;
    }

    /**
     * 使用notbetween验证数据
     * @access protected
     * @param mixed     $value  字段值
     * @param mixed     $rule  验证规则
     * @return bool
     */
    protected function notBetween($value, $rule)
    {
        if (is_string($rule)) {
            $rule = explode(',', $rule);
        }
        list($min, $max) = $rule;
        return $value < $min || $value > $max;
    }

    /**
     * 验证数据长度
     * @access protected
     * @param mixed     $value  字段值
     * @param mixed     $rule  验证规则
     * @return bool
     */
    protected function length($value, $rule)
    {
        if (is_array($value)) {
            $length = count($value);
        } else {
            $length = mb_strlen((string) $value);
        }

        if (strpos($rule, ',')) {
            // 长度区间
            list($min, $max) = explode(',', $rule);
            return $length >= $min && $length <= $max;
        } else {
            // 指定长度
            return $length == $rule;
        }
    }

    /**
     * 验证数据最大长度
     * @access protected
     * @param mixed     $value  字段值
     * @param mixed     $rule  验证规则
     * @return bool
     */
    protected function max($value, $rule)
    {
        if (is_array($value)) {
            $length = count($value);
        } elseif(is_numeric($value)){
            $length = $value;
        } else {
            $length = mb_strlen((string) $value);
        }
        return $length <= $rule;
    }

    /**
     * 验证数据最小长度
     * @access protected
     * @param mixed     $value  字段值
     * @param mixed     $rule  验证规则
     * @return bool
     */
    protected function min($value, $rule)
    {
        if (is_array($value)) {
            $length = count($value);
        } elseif(is_numeric($value)){
            $length = $value;
        } else {
            $length = mb_strlen((string) $value);
        }
        return $length >= $rule;
    }


}